package com.aleyn.router.plug.visitor

import com.aleyn.router.plug.data.HandleModel
import org.gradle.api.file.Directory
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes

/**
 * @author: Aleyn
 * @date: 2023/7/11 11:15
 *
 * ASM ClassVisitor, 这是 LRouter 插件字节码插桩的核心分发器。
 *
 * 该 Visitor 的主要职责是遍历 `LRouterGenerateImpl` 这个自动生成的桩文件中的方法。
 * 当遇到特定的方法（如 `injectAutowired`, `initModuleRouter` 等）时，
 * 它会用一个特定的 `MethodVisitor`（即各种 `InstructAdapter`）来替换原始的 `MethodVisitor`。
 * 这些特定的 Adapter 负责向对应的方法中注入具体的业务逻辑字节码，例如注册路由、拦截器等。
 *
 * 这种设计模式利用了访问者模式和责任链模式，实现了代码注入逻辑的解耦和模块化。
 * 每个 Adapter 只关心自己负责的方法，使得代码更清晰、更易于维护。
 *
 * @param nextVisitor 责任链中的下一个 ClassVisitor。
 * @param allRouterDir 包含了所有中间产物（.txt 缓存文件）的目录列表。
 */
class InsertCodeVisitor(
    nextVisitor: ClassVisitor,
    private val allRouterDir: List<Directory>,
) : ClassVisitor(Opcodes.ASM9, nextVisitor) {

    /**
     * 访问类中的一个方法时被调用。
     *
     * 这是实现方法级别代码注入的关键入口。
     * 通过判断方法名 `name`，将不同的字节码注入任务分发给相应的 `InstructionAdapter`。
     *
     * @return 返回一个 MethodVisitor。如果方法名匹配，则返回一个自定义的 Adapter 用于注入代码；
     *         否则，返回原始的 MethodVisitor，不进行任何修改。
     */
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
        // 根据方法名选择不同的注入适配器
        return when (name) {
            // 为 injectAutowired 方法注入依赖注入逻辑
            "injectAutowired" -> AutowiredInstructAdapter(
                Opcodes.ASM9,
                mv,
                allRouterDir.getTarget()
            )
            // 为 initModuleRouter 方法注入模块路由注册逻辑
            "initModuleRouter" -> ModuleRouterInstructAdapter(
                Opcodes.ASM9,
                mv,
                allRouterDir.getTarget()
            )
            // 为 registerIntercept 方法注入拦截器注册逻辑
            "registerIntercept" -> InterceptInstructAdapter(
                Opcodes.ASM9,
                mv,
                allRouterDir.getTarget()
            )
            // 为 registerAllInitializer 方法注入初始化器注册逻辑
            "registerAllInitializer" -> InitializerInstructAdapter(
                Opcodes.ASM9,
                mv,
                allRouterDir.getTarget()
            )
            // 其他方法不处理，直接返回原始 MethodVisitor
            else -> mv
        }
    }

    /**
     * 一个内联的扩展函数，用于从缓存文件中读取并解析出特定类型的数据模型列表。
     *
     * 这个函数是连接“类扫描”阶段和“代码注入”阶段的桥梁。
     * “类扫描”任务 (`LRouterClassTask`) 将找到的路由、拦截器等信息写入各自的 .txt 文件中。
     * 此函数则负责读取这些文件，将每一行文本解析成相应的 `HandleModel` 数据结构，
     * 以便后续的 `InstructionAdapter` 使用这些数据来生成字节码。
     *
     * @param T 需要解析的目标数据模型类型，如 `HandleModel.Autowired`, `HandleModel.Module` 等。
     * @return 返回一个包含已解析数据模型的列表。
     */
    private inline fun <reified T : HandleModel> List<Directory>.getTarget(): List<T> {
        // 根据泛型类型 T 构建缓存文件名，例如 HandleModel.Autowired -> "autowired.txt"
        val fileName = "${T::class.simpleName!!.lowercase()}.txt"
        return asSequence()
            .filter { it.asFile.exists() } // 过滤存在的目录
            .flatMap { dir -> dir.asFileTree.matching { it.include("**/${fileName}") } } // 在目录中查找匹配的缓存文件
            .flatMap { it.readLines() } // 读取文件所有行
            .filter { it.isNotBlank() } // 过滤空行
            .mapNotNull {
                // 根据泛型类型 T，将行文本解析为具体的数据模型
                when (T::class) {
                    HandleModel.Autowired::class -> {
                        HandleModel.Autowired(it)
                    }

                    HandleModel.Module::class -> {
                        HandleModel.Module(it)
                    }

                    HandleModel.Intercept::class -> {
                        val params = it.split(" ")
                        HandleModel.Intercept(params[0].toByte(), params[1])
                    }

                    HandleModel.Initializer::class -> {
                        val params = it.split(" ")
                        HandleModel.Initializer(params[0].toInt(), params[1].toInt(), params[2])
                    }

                    else -> null
                } as? T
            }.toList()
    }
}